import logging
import typing
from typing import Any, Optional, Text, Tuple, Union, Dict

from rasa.nlu import config
from rasa.nlu.components import ComponentBuilder
from rasa.nlu.config import RasaNLUModelConfig
from rasa.nlu.model import Interpreter, Trainer
from rasa.nlu.training_data import load_data
from rasa.nlu.training_data.loading import load_data_from_endpoint
from rasa.utils.endpoints import EndpointConfig


if typing.TYPE_CHECKING:
    from rasa.importers.importer import TrainingDataImporter

logger = logging.getLogger(__name__)

"""
正式进入NLU训练部分:
    （1）组件构建器：构建组件
    （2）训练器：   训练模型 
    （3）保存器：   保存模型
    （4）解释器：   pipeline处理消息
"""


class TrainingException(Exception):
    """Exception wrapping：异常包装在训练期间可能发生的较低级别的异常
      Attributes:
          failed_target_project --  失败项目的名称
          message               --  解释为什么请求无效的原因
      """

    def __init__(
        self,
        failed_target_project: Optional[Text] = None,
        exception: Optional[Exception] = None,
    ) -> None:
        self.failed_target_project = failed_target_project
        if exception:
            self.message = exception.args[0]
        else:
            self.message = ""

    def __str__(self) -> Text:
        return self.message


def create_persistor(persistor: Optional[Text]):
    """创建远程存储器以存储模型（如果已配置）。"""
    if persistor is not None:
        from rasa.nlu.persistor import get_persistor
        return get_persistor(persistor)
    else:
        return None


async def train(
    nlu_config: Union[Text, Dict, RasaNLUModelConfig],
    data: Union[Text, "TrainingDataImporter"],
    path: Optional[Text] = None,
    fixed_model_name: Optional[Text] = None,
    storage: Optional[Text] = None,
    component_builder: Optional[ComponentBuilder] = None,
    training_data_endpoint: Optional[EndpointConfig] = None,
    persist_nlu_training_data: bool = False,
    **kwargs: Any,
) -> Tuple[Trainer, Interpreter, Optional[Text]]:
    """加载训练器和数据，并运行模型的训练"""

    from rasa.importers.importer import TrainingDataImporter

    # nlu_config必须转换为RasaNLUModelConfig格式
    if not isinstance(nlu_config, RasaNLUModelConfig):
        nlu_config = config.load(nlu_config)

    # 确保我们正在训练可以最终保存的模型。
    # 警告：如果在另一个子流程中训练了具有相同名称的模型，则仍然存在竞争状况

    # 构建训练器：加载数据并加载组件
    trainer = Trainer(nlu_config, component_builder)

    # 构建远程存储器
    persistor = create_persistor(storage)

    # 如果存在训练数据远程端点，则从远程获取训练数据
    if training_data_endpoint is not None:
        training_data = await load_data_from_endpoint(
            training_data_endpoint, nlu_config.language
        )
    elif isinstance(data, TrainingDataImporter):
        training_data = await data.get_nlu_data(nlu_config.data)
    else:
        training_data = load_data(data, nlu_config.language)

    # 打印当前训练数据状况
    training_data.print_stats()

    # 训练器开始训练
    interpreter = trainer.train(training_data, **kwargs)

    if path:
        persisted_path = trainer.persist(
            path, persistor, fixed_model_name, persist_nlu_training_data
        )
    else:
        persisted_path = None

    return trainer, interpreter, persisted_path


if __name__ == "__main__":
    raise RuntimeError(
        "Calling `rasa.nlu.train` directly is no longer supported. Please use "
        "`rasa train` to train a combined Core and NLU model or `rasa train nlu` "
        "to train an NLU model."
    )
